<?php

/**
 * Celebrio Core
 * 
 * @copyright  Copyright (c) 2011 Celebrio Software
 * @package    Celebrio
 * @subpackage Core
 */

namespace Celebrio\Core;

use Nette\Object;
use Nette\Environment;

use \Celebrio\Core\NoSuchServiceException;

/**
 * Handles service registration. For now, the class redirects
 * the requests to the Nette Environment service locator. But
 * all requirements SHOULD be handled via this class since there
 * are some implementation changes in Nette 2.0
 *
 * @abstract
 * 
 * @author pavel
 */
abstract class ServiceProvider extends Object {

    private static $serviceManifests = array();

    /**
     * Registers the service. Uses Nette service locator
     * 
     * @param Manifest $manifest manifest identifying the module
     */
    public static function registerService(Manifest $manifest) {

        // we don't want to register abstract module
        if ($manifest->isAbstract())
            return;

        $serviceName = $manifest->getInterfaceFullNameDots();

        if (isset(static::$serviceManifests[$serviceName])) {
            $knownServices = static::$serviceManifests[$serviceName];
            if (is_array($knownServices)) {
                $knownServices[] = $manifest;
            } else {
                $knownServices = array($knownServices, $manifest);
            }
            static::$serviceManifests[$serviceName] = $knownServices;
            //throw new \Exception("Service with name $serviceName already assigned.");
        } else {
            static::$serviceManifests[$serviceName] = $manifest;
        }

        Environment::getServiceLocator()->addService(
                $manifest->getImplementationFullName(), $manifest->getImplementationFullName());

//        if (Environment::getServiceLocator()->hasService($serviceName)) {
//            //Environment::getServiceLocator()->removeService($serviceName);
//            throw new \Exception("Service $serviceName already exists,
//                    maybe more implementations of the same service are activated.
//                    TODO implement :)");
//        }
    }

    public static function getService($interfaceName) {
        $serviceManifest = static::getManifestFor($interfaceName);
        return Environment::getService($serviceManifest->getImplementationFullName());
    }

    public static function getServiceName($interfaceName) {
        $serviceManifest = static::getManifestFor($interfaceName);
        return $serviceManifest->getImplementation();
    }

    public static function getAllServices($interfaceName) {
        $callback = function($manifest) { 
                return Environment::getService($manifest->getImplementationFullName());
            };
        return static::workWithMultiple($interfaceName, $callback);
    }
   
    public static function getAllServiceNames($interfaceName) {
        $callback = function($manifest) { 
                return $manifest->getImplementationFullName();
            };
        return static::workWithMultiple($interfaceName, $callback);
    }
    
    /**
     * Looks for the possible multiple services implementing interface
     * given as first parameter. If only one is found, callback is applied
     * to its manifest object and result returned. If more than one service
     * are found, callback is applied to all of them (manifests) and array
     * of callback results is returned.
     * 
     * @param string $interfaceName
     * @param function $callback
     * @return \array
     */
    private static function workWithMultiple($interfaceName, $callback) {
        $mulManifest = static::getManifestFor($interfaceName, true);
        if (!is_array($mulManifest)) {
            $mulManifest = array($mulManifest);
        }
        $services = array();
        foreach($mulManifest as $manifest) {
            $services[] = $callback($manifest);
        }
        return $services;
    }

    /**
     * @static
     * @throws \Exception
     * @param $serviceName
     * @param bool $all
     * @return \array(Manifest)|Manifest
     */
    private static function getManifestFor($serviceName, $all = false) {

        if (!isset(static::$serviceManifests[$serviceName])) {
            throw new NoSuchServiceException("No such service $serviceName");
        } else {
            $possibleMultiple = static::$serviceManifests[$serviceName];

            if (!is_array($possibleMultiple) || $all) {
                $resultService = $possibleMultiple;
            } elseif($all == false) {
                throw new \Exception("More services $serviceName found but only 1 required");
                $possibleMultiple = sort($possibleMultiple);
                $resultService = $possibleMultiple[0]; // first service is returned
            }
            return $resultService;
        }
    }

}

